package org.basis.netty.chat.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.basis.netty.chat.message.*;
import org.basis.netty.chat.protocol.MessageCodecSharable;
import org.basis.netty.chat.protocol.ProcotolFrameDecoder;

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 聊天客户端
 *
 * @author Mr_wenpan@163.com 2021/09/23 11:20
 */
@Slf4j
public class ChatClient {

    public static void main(String[] args) {

        NioEventLoopGroup group = new NioEventLoopGroup();
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);
        final MessageCodecSharable messageCodecSharable = new MessageCodecSharable();
        final CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);
        final AtomicBoolean LOGIN = new AtomicBoolean(false);
        final AtomicBoolean EXIT = new AtomicBoolean(false);
        final Scanner scanner = new Scanner(System.in);

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.group(group);
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    // 添加自定义预设长度解码器（规定如何读取数据）
                    pipeline.addLast(new ProcotolFrameDecoder());
//                    pipeline.addLast(loggingHandler);
                    // 入站出站消息编码解码器
                    pipeline.addLast(messageCodecSharable);

                    // 心跳检查handler
                    // 用来判断是不是 读空闲时间过长，或 写空闲时间过长
                    // 3s 内如果没有向服务器写数据，会触发一个 IdleState#WRITER_IDLE 事件
                    pipeline.addLast(new IdleStateHandler(0, 3000, 0));
                    // ChannelDuplexHandler 可以同时作为入站和出站处理器
                    pipeline.addLast(new ChannelDuplexHandler() {

                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                            IdleStateEvent event = (IdleStateEvent) evt;
                            // 收到写事件，如果超过3s还没有向server写数据，则触发这里的事件，发送给server一个ping消息
                            if (event.state() == IdleState.WRITER_IDLE) {
                                ctx.writeAndFlush(new PingMessage());
                            }
                        }

                    });


                    // 聊天业务handler
                    pipeline.addLast("client handler", new ChannelInboundHandlerAdapter() {

                        /**与服务端连接正常断开时吊起这个方法(保证读取用户输入的线程正常退出)*/
                        @Override
                        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                            log.info("连接已经断开，按任意键退出..");
                            EXIT.set(true);
                        }

                        /**与服务端连接异常断开时吊起这个方法(保证读取用户输入的线程正常退出)*/
                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                            log.info("连接异常断开，按任意键退出..");
                            EXIT.set(true);
                        }

                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//                            log.info("msg = {}", msg);
                            // 登录响应消息
                            if (msg instanceof LoginResponseMessage) {
                                LoginResponseMessage response = (LoginResponseMessage) msg;
                                // 登录成功
                                if (response.isSuccess()) {
                                    LOGIN.set(true);
                                }
                                // 唤醒等待登录的线程
                                WAIT_FOR_LOGIN.countDown();
                            }
                        }

                        /**连接建立后被吊起*/
                        @Override
                        public void channelActive(final ChannelHandlerContext ctx) throws Exception {
                            // 负责接收用户在控制台的输入，负责向服务器发送各种消息
                            new Thread(() -> {
                                System.out.println("请输入用户名：");
                                String username = scanner.nextLine();
                                // 退出客户端
                                if (EXIT.get()) {
                                    return;
                                }
                                System.out.println("请输入密码：");
                                String password = scanner.nextLine();
                                // 退出客户端
                                if (EXIT.get()) {
                                    return;
                                }

                                // 构造消息对象
                                LoginRequestMessage loginRequestMessage = new LoginRequestMessage(username, password);
                                System.out.println("登录消息对象 ： " + loginRequestMessage);

                                // 发送登录请求
                                ctx.writeAndFlush(loginRequestMessage);
                                System.out.println("等待后续操作.........");

                                try {
                                    WAIT_FOR_LOGIN.await();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                // 如果登录失败，则结束线程并退出客户端
                                if (!LOGIN.get()) {
                                    ctx.channel().close();
                                    return;
                                }

                                while (true) {
                                    System.out.println("==================================");
                                    System.out.println("send [username] [content]");
                                    System.out.println("gsend [group name] [content]");
                                    System.out.println("gcreate [group name] [m1,m2,m3...]");
                                    System.out.println("gmembers [group name]");
                                    System.out.println("gjoin [group name]");
                                    System.out.println("gquit [group name]");
                                    System.out.println("quit");
                                    System.out.println("==================================");
                                    String command;
                                    try {
                                        command = scanner.nextLine();
                                    } catch (Exception e) {
                                        break;
                                    }
                                    if (EXIT.get()) {
                                        return;
                                    }
                                    // 拆分用户输入的命令
                                    String[] s = command.split(" ");

                                    switch (s[0]) {
                                        case "send":
                                            ctx.writeAndFlush(new ChatRequestMessage(username, s[1], s[2]));
                                            break;
                                        case "gsend":
                                            ctx.writeAndFlush(new GroupChatRequestMessage(username, s[1], s[2]));
                                            break;
                                        case "gcreate":
                                            HashSet<String> set = new HashSet<>(Arrays.asList(s[2].split(",")));
                                            // 把自己加入到群里中
                                            set.add(username);
                                            ctx.writeAndFlush(new GroupCreateRequestMessage(s[1], set));
                                            break;
                                        case "gmembers":
                                            ctx.writeAndFlush(new GroupMembersRequestMessage(s[1]));
                                            break;
                                        case "gjoin":
                                            ctx.writeAndFlush(new GroupJoinRequestMessage(username, s[1]));
                                            break;
                                        case "gquit":
                                            ctx.writeAndFlush(new GroupQuitRequestMessage(username, s[1]));
                                            break;
                                        case "quit":
                                            // 关闭通道
                                            ctx.channel().close();
                                            return;
                                    }
                                }
                            }, "system in").start();

                        }
                    });

                }
            });

            // 阻塞等待关闭
            ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("localhost", 8080)).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (Exception ex) {
            log.error("client error ", ex);
        } finally {
            group.shutdownGracefully();
        }

    }

}
